/*! \file HXModel.h
    \brief 3D model representation and file interface.

	Head X | Flinders University | Martin Luerssen | 2009+
*/

#pragma once

class HXModel;

#include "HXUtil.h"
#include "HXMatrix.h"

#define HXMODEL_ID "HXMODEL_1.0"
#define HXM_TEXTURE 0x00000001

//-----------------------------------------------------------------------------

//! Possible render texture modes for a 3D model.
enum HXTextureMode
{
	HXTM_NOTEXTURE,
	HXTM_PERVERTEX,
	HXTM_PERFACET_SINGLECOORD,
	HXTM_PERFACET_MULTICOORD,
	HXTM_INVALID
};

//-----------------------------------------------------------------------------

struct HXRGBA
{
	HXRGBA() { for (size_t i = 0; i < 4; i++) component[i] = 0; }
	unsigned char component[4];
};

class HXImageRGBA
{
public:
	HXImageRGBA() : m_pData(NULL), m_height(0), m_width(0) {}
	HXImageRGBA(const HXImageRGBA& other) : m_pData(NULL) { CopyFrom(other.m_pData, other.m_width, other.m_height); }
	~HXImageRGBA() { delete [] m_pData; }
	void Clear() { m_height = 0; m_width = 0; delete [] m_pData; m_pData = NULL; }
	void CopyFrom(void* m_pOtherData, size_t width, size_t height);
	void AdjustAlpha(float alpha_scale);
	void MorphWith(void* m_pOtherData, float blend_frac, size_t otherWidth, size_t otherHeight);
	bool IsEmpty() const { return !m_width || !m_height; }
	void FlipHorizontal();
	const HXRGBA* GetData() { return m_pData; }
	size_t GetDataSize() const { return m_width * m_height * sizeof(HXRGBA); }
	size_t GetHeight() const { return m_height; }
	size_t GetWidth() const { return m_width; }
	size_t GetSize() const { return m_width * m_height; }
	bool LoadFromFile(const wstring &fname, float alpha_scale = 1.0f);
	void Resize(size_t width, size_t height);
	
	void FromBinaryStream(istream& in);
	void ToBinaryStream(ostream& out);

protected:
	HXRGBA* m_pData;
	size_t m_height, m_width;
};

//-----------------------------------------------------------------------------

class HXMaterial
{
public:
	HXMaterial() 
	: oglTextureName(0), 
	  ambient(1.0f, 1.0f, 1.0f), 
	  diffuse(1.0f, 1.0f, 1.0f),
	  specular(0.0f, 0.0f, 0.0f),
	  shininess(1.0f) {}

	void FromBinaryStream(istream& in);
	void ToBinaryStream(ostream& out);

	unsigned int				oglTextureName;
	HXImageRGBA					texture;
	HXVector3F					ambient, diffuse, specular;
	float						shininess;
};

//-----------------------------------------------------------------------------

struct HXModelHeader
{
	streampos nextModel;
	float alphaThreshold;
	float shininess;
	HXTextureMode textureMode;
    unsigned long vertexCount;
	unsigned long vertexAdded;
    unsigned long triangleCount;
    unsigned long quadCount;
    unsigned long texturePointCount;
	unsigned long texturePointCountEx;
	unsigned long materialCount;
	unsigned long materialTriangleCount;
	unsigned long materialQuadCount;
    unsigned long diffMorphCount;
    unsigned long statMorphCount;
    unsigned long statMorphVertexCount;
};

struct HXVertexLabel
{
	HXVertexLabel() : idx(0) {}

	void FromBinaryStream(istream& in)
	{
		in.read(reinterpret_cast<char *>(&idx), sizeof(unsigned long));
		size_t labelLength;
		in.read(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		wlabel.clear();
		for (; labelLength > 0; labelLength--)
		{
			wchar_t wc;
			in.read(reinterpret_cast<char *>(&wc), sizeof(wchar_t));
			wlabel.push_back(wc);
		}
	}
	void ToBinaryStream(ostream& out)
	{
		out.write(reinterpret_cast<char *>(&idx), sizeof(unsigned long));
		size_t labelLength = wlabel.size();
		out.write(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		out.write(reinterpret_cast<const char *>(wlabel.c_str()), wlabel.size()*sizeof(wchar_t));
	}

    unsigned long idx;
    wstring wlabel;
};

struct HXSurfaceLabel
{
	HXSurfaceLabel() : idx(0) {}

	void FromBinaryStream(istream& in)
	{
		in.read(reinterpret_cast<char *>(&idx), sizeof(unsigned long));
		size_t labelLength;
		in.read(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		wlabel.clear();
		for (; labelLength > 0; labelLength--)
		{
			wchar_t wc;
			in.read(reinterpret_cast<char *>(&wc), sizeof(wchar_t));
			wlabel.push_back(wc);
		}
		vertexInterpolationWeight.FromBinaryStream(in);
	}
	void ToBinaryStream(ostream& out)
	{
		out.write(reinterpret_cast<char *>(&idx), sizeof(unsigned long));
		size_t labelLength = wlabel.size();
		out.write(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		out.write(reinterpret_cast<const char *>(wlabel.c_str()), wlabel.size()*sizeof(wchar_t));
		vertexInterpolationWeight.ToBinaryStream(out);
	}

    unsigned long idx;
    wstring wlabel;
	HXVector3F vertexInterpolationWeight;
};

struct HXDiffMorph
{
	void FromBinaryStream(istream& in)
	{
		size_t labelLength;
		in.read(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		wlabel.clear();
		for (; labelLength > 0; labelLength--)
		{
			wchar_t wc;
			in.read(reinterpret_cast<char *>(&wc), sizeof(wchar_t));
			wlabel.push_back(wc);
		}
		size_t listLength;
		in.read(reinterpret_cast<char *>(&listLength), sizeof(size_t));
		vertexDeltaList.clear();
		for (; listLength > 0; listLength--)
		{
			HXVector3F v3;
			v3.FromBinaryStream(in);
			vertexDeltaList.push_back(v3);
		}		
	}
	void ToBinaryStream(ostream& out)
	{
		size_t labelLength = wlabel.size();
		out.write(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		out.write(reinterpret_cast<const char *>(wlabel.c_str()), wlabel.size()*sizeof(wchar_t));
		size_t listLength = vertexDeltaList.size();
		out.write(reinterpret_cast<char *>(&listLength), sizeof(size_t));
		for (size_t i = 0; i < vertexDeltaList.size(); i++)
			vertexDeltaList[i].ToBinaryStream(out);
	}

    wstring wlabel;
    vector<HXVector3F> vertexDeltaList;
};

struct HXStatMorph
{
	HXStatMorph() : firstVertexOffset(0) {}

	void FromBinaryStream(istream& in)
	{
		size_t labelLength;
		in.read(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		wlabel.clear();
		for (; labelLength > 0; labelLength--)
		{
			wchar_t wc;
			in.read(reinterpret_cast<char *>(&wc), sizeof(wchar_t));
			wlabel.push_back(wc);
		}
		in.read(reinterpret_cast<char *>(&firstVertexOffset), sizeof(unsigned long));
		size_t listLength;
		in.read(reinterpret_cast<char *>(&listLength), sizeof(size_t));
		vertexIdxList.clear();
		for (; listLength > 0; listLength--)
		{
			unsigned long idx;
			in.read(reinterpret_cast<char *>(&idx), sizeof(unsigned long));
			vertexIdxList.push_back(idx);
		}		
	}
	void ToBinaryStream(ostream& out)
	{
		size_t labelLength = wlabel.size();
		out.write(reinterpret_cast<char *>(&labelLength), sizeof(size_t));
		out.write(reinterpret_cast<const char *>(wlabel.c_str()), wlabel.size()*sizeof(wchar_t));
		out.write(reinterpret_cast<char *>(&firstVertexOffset), sizeof(unsigned long));
		size_t listLength = vertexIdxList.size();
		out.write(reinterpret_cast<char *>(&listLength), sizeof(size_t));
		for (size_t i = 0; i < vertexIdxList.size(); i++)
			out.write(reinterpret_cast<char *>(&vertexIdxList[i]), sizeof(unsigned long));
	}

	wstring wlabel;
	unsigned long firstVertexOffset;
    vector<unsigned long> vertexIdxList;
};

//-----------------------------------------------------------------------------

class HXModel
{
public:
	HXModel() : alphaThreshold(1.0f), textureCount(0), textureMode(HXTM_INVALID), modelVertexSize(0) {}

	bool ImportFromObjFile(const wstring& fname);
	bool ImportObjBlendShape(const wstring& wlabel, const wstring& fname);
	bool IsFileAModel(const wstring& fname);
	bool LoadFromFile(const wstring& fname, size_t organIndex = 0);
	bool SaveToFile(const wstring& fname);
	static bool SaveToFile(const wstring& fname, list<HXModel*>& modelList);
	void SaveToFileStream(fstream& fstr, bool bSubsequentModel);
	void Finalize(bool adjustTextures);

	// Stored data
	vector<HXVector3F>			vertexList;
	vector<HXVector3F>			statVertexList;
	vector<int>					vertexAdded;
    vector<HXVectorTriangle>	triangleList;
    vector<HXVectorQuad>		quadList;
    vector<HXVector2F>			textureList;
	vector<HXVector2F>			expandedTextureList;
    vector<HXVectorTriangle>	textureTriangleList;
    vector<HXVectorQuad>		textureQuadList;
	vector<HXMaterial>			materialList;
	vector<int>					materialTriangleList;
	vector<int>					materialQuadList;
    vector<HXDiffMorph>			diffMorphList;
    vector<HXStatMorph>			statMorphList;
	float						alphaThreshold;

	// Ephemeral data (generated by Finalize(), except for textureCount)
	vector<size_t>				materialTriangleSegments;
	vector<size_t>				materialQuadSegments;
	size_t						textureCount;
	HXTextureMode				textureMode;
	unsigned long				modelVertexSize;
};

//-----------------------------------------------------------------------------